﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Reflection;
using Beaker;
using Beaker.Reflection;

namespace OpenCube.Nbt.Mapping.Converters
{
	/// <summary>
	/// Generic conversion from any enumerable to the integer value and back
	/// </summary>
	public sealed class EnumIntegerConverter<TEnum>
		: NbtMultiTypeConverter
	{
		public EnumIntegerConverter()
		{
			Contract.Requires<ArgumentException>(
				typeof(TEnum).GetTypeInfo().IsEnum
				|| (typeof(TEnum).GetTypeInfo().IsMaybe() && typeof(TEnum).GetUnderlyingType().GetTypeInfo().IsEnum)
				|| (typeof(TEnum).GetTypeInfo().IsNullable() && typeof(TEnum).GetUnderlyingType().GetTypeInfo().IsEnum),
				"The TEnum type should be an enumeration."
			);
		}

		protected override bool CanConvertToModel(IReadOnlyList<Type> sourceNbtTypes, Type targetModelType)
		{
			// invalid source. Only ints are auto converted
			if (sourceNbtTypes.Count != 1 || sourceNbtTypes[0] != typeof(int))
				return false;

			// invalid target. Only ints are only converted to Enums.
			return targetModelType == typeof(TEnum)
				  || (targetModelType.GetTypeInfo().IsMaybe() && targetModelType.GetUnderlyingType() == typeof(TEnum))
				  || (targetModelType.GetTypeInfo().IsNullable() && targetModelType.GetUnderlyingType() == typeof(TEnum));
		}

		protected override bool CanConvertToNbt(Type sourceModel)
		{
			throw new NotImplementedException();
		}

		protected override object ConvertToModelValue(IConversionContext conversionContext, IReadOnlyList<object> nbtValue)
		{
			var value = (int)nbtValue[0];
			TypeInfo typeInfo = typeof(TEnum).GetTypeInfo();

			if (typeInfo.IsMaybe())
			{
				Type enumType = Maybe.GetUnderlyingType(typeof(TEnum));
				object enumValue = GetEnumValue(value, enumType);
				return (TEnum)Maybe.Create(enumType, enumValue);
			}

			if (typeInfo.IsNullable())
			{
				Type enumType = Nullable.GetUnderlyingType(typeof(TEnum));
				object enumValue = GetEnumValue(value, enumType);
				return (TEnum)enumValue; // C#/CLR will auto convert to nullable typess during unbox
			}

			return (TEnum)GetEnumValue(value, typeof(TEnum));
		}

		/// <summary>
		/// Get the value of the maybe while checking bounds of the Enum
		/// </summary>
		/// <param name="nbtValue">The maybe containing the NBT value from the dictionary.</param>
		/// <param name="enumType">The type of the enum.</param>
		/// <returns>The enum value.</returns>
		/// <exception cref="ArgumentOutOfRangeException">Throw when the input NBT value was out of range for the target Enum.</exception>
		private static object GetEnumValue(int nbtValue, Type enumType)
		{
			object value = Enum.ToObject(enumType, nbtValue);
			if (!Enum.IsDefined(enumType, value))
				throw new ArgumentOutOfRangeException("nbtValue", nbtValue, "The provided value is out of range of the " + enumType.Name);
			return value;
		}

		protected override IEnumerable<object> ConvertToNbtValue(IConversionContext conversionContext, object modelValue)
		{
			throw new NotImplementedException();
		}
	}
}
